﻿#region Using Statements
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Input;
using System.Reflection;
using System.Linq;

#endregion

// based loosely on http://www.ziggyware.com/readarticle.php?article_id=209 (see comments)

#region COPYRIGHT

/*
    Copyright (c) 2007, 2008, 2009, 2010 
        Roland Rosenkranz (Glatzemann@email.de)
*/

#endregion

#region LICENSE

/*
    This file is part of starLiGHT.Input.

    starLiGHT.Input is free software: you can redistribute it and/or modify
    it under the terms of the GNU Lesser General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    starLiGHT.Input is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU Lesser General Public License for more details.

    You should have received a copy of the GNU Lesser General Public License
    along with starLiGHT.Input.  If not, see <http://www.gnu.org/licenses/>.

    
    ADDITIONAL (commercial) LICENSES for starLiGHT.Input are available on request.
*/

#endregion

#region Version Stuff
// **************[ starLiGHT.Engine SVN ]**********************
// * $Rev:: 1652                  $:  Revision of last commit *
// * $Author:: glatzemann         $:  Author of last commit   *
// * $Date:: 2010-11-16 07:41:22 #$:  Date of last commit     *
// ************************************************************
#endregion

namespace starLiGHT.Input
{
    public enum MultitouchIndex
    {
        ONE,
        TWO,
        THREE,
        FOUR
    }

    public enum AccelerometerType
    {
        RAW,
        SMOOTHED
    }

    public enum MouseAxis
    {
        POSITION,
        WHEEL,
    }

    public enum GamePadAxis
    {
        LEFT_STICK,
        RIGHT_STICK,
        LEFT_TRIGGER,
        RIGHT_TRIGGER,
    }

    public class InputManager : GameComponent
    {
#region Member Variables
        DeviceManager inputDeviceManager;
        ActionManager actionManager;
        List<ControlScheme> controlSchemes;
        string activeControlScheme;
#endregion

        readonly Keys[] _keys = new Keys[255];
        readonly float[] keyTimeAccumulator = new float[255];
        readonly float[] keyRepeatAccumulator = new float[255];

        KeyboardState currentKeyState;
        KeyboardState previousKeyState;
        float keyRepeatRate = 0.5f;

        //Dictionary<string, InputTriggerType> actions;
        //Dictionary<string, float> actionTimeAccumulator;
        //Dictionary<string, float> actionRepeatAccumulator;

        #region Constructors
        public InputManager(Microsoft.Xna.Framework.Game game)
            : base(game)
        {
            init();
        }

        public InputManager(Microsoft.Xna.Framework.Game game, float keyRepeatRate)
            : base(game)
        {
            this.keyRepeatRate = keyRepeatRate;
            init();
        }

        private void init()
        {
            inputDeviceManager = new DeviceManager();
            actionManager = new ActionManager();

            //
            // Keyboard stuff
            //

//#if XBOX360 || ZUNE
//            foreach (Keys key in GetValues(new Keys()))
//#else
//            foreach (Keys key in Enum.GetValues(typeof(Keys)))
//#endif
//            {
//                _keys[(int)key] = key;
//            }

//            previousKeyState = currentKeyState = Keyboard.GetState();

//            foreach (Keys key in _keys)
//            {
//                keyTimeAccumulator[(int)key] = 0.0f;
//                keyRepeatAccumulator[(int)key] = 0.0f;
//            }

            ////
            //// Actions + Action Mappings
            ////

            //actions = new Dictionary<string, InputTriggerType>();
            //actionRepeatAccumulator = new Dictionary<string, float>();
            //actionTimeAccumulator = new Dictionary<string, float>();
        }

//#if XBOX360 || ZUNE
//        private IEnumerable<Enum> GetValues(Enum enumeration)
//        {
//            List<Enum> enumerations = new List<Enum>();
//            foreach (FieldInfo fieldInfo in enumeration.GetType().GetFields(BindingFlags.Static | BindingFlags.Public))
//            {
//                enumerations.Add((Enum)fieldInfo.GetValue(enumeration));
//            }
//            return enumerations;
//        }
//#endif
        
        #endregion

        public override void Update(GameTime gameTime)
        {
            inputDeviceManager.Update(gameTime);
            actionManager.Update(gameTime);



            float elapsedTime = (float)gameTime.ElapsedGameTime.TotalSeconds;

            //
            // Keyboard stuff
            //

            //previousKeyState = currentKeyState;
            //currentKeyState = Keyboard.GetState();

            //foreach (Keys key in _keys)
            //{
            //    if (currentKeyState.IsKeyDown(key))
            //    {
            //        keyTimeAccumulator[(int)key] += elapsedTime;
            //        keyRepeatAccumulator[(int)key] += elapsedTime;
            //    }
            //    else
            //    {
            //        keyTimeAccumulator[(int)key] = 0.0f;
            //        keyRepeatAccumulator[(int)key] = 0.0f;
            //    }
            //}

            //
            // check if control scheme events needs to be triggered...
            //

//            if (ActiveControlScheme != null)
//            {
//                bool triggerAction;
//                foreach (KeyValuePair<ControlSchemeButton, string> kvp in ActiveControlScheme.actionMap)
//                {
//                    triggerAction = false;

//                    //
//                    // check gamepad action mappings
//                    //

//                    //if (kvp.Key.gamePadButton != null)
//                    //{
//                        //if (IsButtonPress(kvp.Key.gamePadButton.Value))
//                        //{
//                        //    actions[kvp.Value] = InputTriggerType.Pressed;
//                        //    controlScheme.TriggerAction(this, kvp.Key, InputTriggerType.Pressed, TimeSpan.Zero);
//                        //    triggerAction = true;
//                        //}
//                        //else if (IsButtonRelease(kvp.Key.gamePadButton.Value))
//                        //{
//                        //    actions[kvp.Value] = InputTriggerType.Released;
//                        //    controlScheme.TriggerAction(this, kvp.Key, InputTriggerType.Released, TimeSpan.Zero);
//                        //    triggerAction = true;
//                        //}
//                        //else if (IsButtonHeld(kvp.Key.gamePadButton.Value))
//                        //{
//                        //    actionRepeatAccumulator[kvp.Value] += elapsedTime;
//                        //    actionTimeAccumulator[kvp.Value] += elapsedTime;
//                        //    if (actionRepeatAccumulator[kvp.Value] >= keyRepeatRate && kvp.Key.autoRepeat)
//                        //    {
//                        //        actionRepeatAccumulator[kvp.Value] -= keyRepeatRate;
//                        //        actions[kvp.Value] = InputTriggerType.Pressed;
//                        //        controlScheme.TriggerAction(this, kvp.Key, InputTriggerType.Pressed, new TimeSpan(0, 0, 0, 0, (int)(actionTimeAccumulator[kvp.Value] * 1000.0f)));
//                        //    }
//                        //    else
//                        //    {
//                        //        actions[kvp.Value] = InputTriggerType.Held;
//                        //        controlScheme.TriggerAction(this, kvp.Key, InputTriggerType.Held, new TimeSpan(0, 0, 0, 0, (int)(actionTimeAccumulator[kvp.Value] * 1000.0f)));
//                        //    }
//                        //    triggerAction = true;
//                        //}
//                    //}

//#if !XBOX360
//                    //
//                    // check mouse action mappings
//                    //

//                    //if (!triggerAction && kvp.Key.mouseButton != null)
//                    //{
//                        //if (IsMouseButtonPress(kvp.Key.mouseButton.Value))
//                        //{
//                        //    actions[kvp.Value] = InputTriggerType.Pressed;
//                        //    controlScheme.TriggerAction(this, kvp.Key, InputTriggerType.Pressed, TimeSpan.Zero);
//                        //    triggerAction = true;
//                        //}
//                        //else if (IsMouseButtonRelease(kvp.Key.mouseButton.Value))
//                        //{
//                        //    actions[kvp.Value] = InputTriggerType.Released;
//                        //    controlScheme.TriggerAction(this, kvp.Key, InputTriggerType.Released, TimeSpan.Zero);
//                        //    triggerAction = true;
//                        //}
//                        //else if (IsMouseButtonHeld(kvp.Key.mouseButton.Value))
//                        //{
//                        //    actionRepeatAccumulator[kvp.Value] += elapsedTime;
//                        //    actionTimeAccumulator[kvp.Value] += elapsedTime;
//                        //    if (actionRepeatAccumulator[kvp.Value] >= keyRepeatRate && kvp.Key.autoRepeat)
//                        //    {
//                        //        actionRepeatAccumulator[kvp.Value] -= keyRepeatRate;
//                        //        actions[kvp.Value] = InputTriggerType.Pressed;
//                        //        controlScheme.TriggerAction(this, kvp.Key, InputTriggerType.Pressed, new TimeSpan(0, 0, 0, 0, (int)(actionTimeAccumulator[kvp.Value] * 1000.0f)));
//                        //    }
//                        //    else
//                        //    {
//                        //        actions[kvp.Value] = InputTriggerType.Held;
//                        //        controlScheme.TriggerAction(this, kvp.Key, InputTriggerType.Held, new TimeSpan(0, 0, 0, 0, (int)(actionTimeAccumulator[kvp.Value] * 1000.0f)));
//                        //    }
//                        //    triggerAction = true;
//                        //}
//                    //}

//                    //if (!triggerAction) actionTimeAccumulator[kvp.Value] = 0.0f;
//#endif

//                    //
//                    // check keyboard action mappings
//                    //

//                    //if (!triggerAction && kvp.Key.key != null)
//                    //{
//                    //    if (currentKeyState.IsKeyDown(kvp.Key.key.Value) && previousKeyState.IsKeyUp(kvp.Key.key.Value))
//                    //    {
//                    //        actions[kvp.Value] = InputTriggerType.Pressed;
//                    //        ActiveControlScheme.TriggerAction(this, kvp.Key, InputTriggerType.Pressed, TimeSpan.Zero);
//                    //    }
//                    //    else if (currentKeyState.IsKeyUp(kvp.Key.key.Value) && previousKeyState.IsKeyDown(kvp.Key.key.Value))
//                    //    {
//                    //        actions[kvp.Value] = InputTriggerType.Released;
//                    //        ActiveControlScheme.TriggerAction(this, kvp.Key, InputTriggerType.Released, TimeSpan.Zero);
//                    //    }
//                    //    else if (currentKeyState.IsKeyDown(kvp.Key.key.Value) && previousKeyState.IsKeyDown(kvp.Key.key.Value))
//                    //    {
//                    //        actionRepeatAccumulator[kvp.Value] += elapsedTime;
//                    //        actionTimeAccumulator[kvp.Value] += elapsedTime;
//                    //        if (actionRepeatAccumulator[kvp.Value] >= keyRepeatRate && kvp.Key.autoRepeat)
//                    //        {
//                    //            actionRepeatAccumulator[kvp.Value] -= keyRepeatRate;
//                    //            actions[kvp.Value] = InputTriggerType.Pressed;
//                    //            ActiveControlScheme.TriggerAction(this, kvp.Key, InputTriggerType.Pressed, new TimeSpan(0, 0, 0, 0, (int)keyTimeAccumulator[(int)kvp.Key.key.Value] * 1000));
//                    //        }
//                    //        else
//                    //        {
//                    //            actions[kvp.Value] = InputTriggerType.Held;
//                    //            ActiveControlScheme.TriggerAction(this, kvp.Key, InputTriggerType.Held, new TimeSpan(0, 0, 0, 0, (int)keyTimeAccumulator[(int)kvp.Key.key.Value] * 1000));
//                    //        }
//                    //        triggerAction = true;
//                    //    }
//                    //}

//                }
//            }

            base.Update(gameTime);
        }

#region Action handling

        /// <summary>
        /// Creates a new InputAction without any bindings.
        /// </summary>
        /// <param name="actionName">The (unique) name of the InputAction.</param>
        /// <param name="actionType">The type of the InputAction. This is used to select a specialized subclass.</param>
        /// <returns>The newly created InputAction of the given type with the given name.</returns>
        public InputAction createAction(string actionName, Type actionType)
        {
            return actionManager.createAction(actionName, actionType);
        }

        /// <summary>
        /// Creates a new InputAction without any bindings bound to a specific player.
        /// </summary>
        /// <param name="actionName">The (unique) name of the InputAction.</param>
        /// <param name="actionType">The type of the InputAction. This is used to select a specialized subclass.</param>
        /// <returns>The newly created InputAction of the given type with the given name.</returns>
        public InputAction createAction(string actionName, Type actionType, LogicalPlayerIndex logicalPlayer)
        {
            return actionManager.createAction(actionName, actionType, logicalPlayer);
        }

        public bool IsActionPress(string action)
        {
            ActionPlayerIndex api = new ActionPlayerIndex(action, null);
            if (!actionManager.actions.ContainsKey(api)) return false;
            return actionManager.actions[api].IsActionPressed();
        }

        public bool IsActionPress(string action, LogicalPlayerIndex logicalPlayer)
        {
            ActionPlayerIndex api = new ActionPlayerIndex(action, logicalPlayer);
            if (!actionManager.actions.ContainsKey(api)) return false;
            return actionManager.actions[api].IsActionPressed();
        }

        public bool IsActionRelease(string action)
        {
            ActionPlayerIndex api = new ActionPlayerIndex(action, null);
            if (!actionManager.actions.ContainsKey(api)) return false;
            return actionManager.actions[api].IsActionReleased();
        }

        public bool IsActionRelease(string action, LogicalPlayerIndex logicalPlayer)
        {
            ActionPlayerIndex api = new ActionPlayerIndex(action, logicalPlayer);
            if (!actionManager.actions.ContainsKey(api)) return false;
            return actionManager.actions[api].IsActionReleased();
        }

        public bool IsActionHeld(string action)
        {
            ActionPlayerIndex api = new ActionPlayerIndex(action, null);
            if (!actionManager.actions.ContainsKey(api)) return false;
            return actionManager.actions[api].IsActionHeld();
        }

        public bool IsActionHeld(string action, LogicalPlayerIndex logicalPlayer)
        {
            ActionPlayerIndex api = new ActionPlayerIndex(action, logicalPlayer);
            if (!actionManager.actions.ContainsKey(api)) return false;
            return actionManager.actions[api].IsActionHeld();
        }

        public Vector2 ActionPosition(string action)
        {
            ActionPlayerIndex api = new ActionPlayerIndex(action, null);
            if (!actionManager.actions.ContainsKey(api)) return Vector2.Zero;
            return actionManager.actions[api].Position;
        }

        public Vector2 ActionPosition(string action, LogicalPlayerIndex logicalPlayer)
        {
            ActionPlayerIndex api = new ActionPlayerIndex(action, logicalPlayer);
            if (!actionManager.actions.ContainsKey(api)) return Vector2.Zero;
            return actionManager.actions[api].Position;
        }

        //public void addAction(string action, ControlSchemeButton csb)
        //{
        //    if (ActiveControlScheme == null) ActiveControlScheme = new ControlScheme("DEFAULT");
            
        //    if (!actions.ContainsKey(action)) actions.Add(action, InputTriggerType.None);
        //    if (!actionRepeatAccumulator.ContainsKey(action)) actionRepeatAccumulator.Add(action, 0.0f);
        //    if (!actionTimeAccumulator.ContainsKey(action)) actionTimeAccumulator.Add(action, 0.0f);
        //    ActiveControlScheme.SetAction(csb, action);
        //}

#endregion

        /// <summary>
        /// Returns the input device of the given type of the given player
        /// </summary>
        /// <param name="logicalPlayer">The LogicalPlayerIndex of the player whose device you want to get.</param>
        /// <param name="deviceType">The type of the device you want to get.</param>
        /// <returns>The requested input device of type IControlDevice.</returns>
        public IControlDevice GetInputDevice(LogicalPlayerIndex logicalPlayer, DeviceType deviceType)
        {
            return inputDeviceManager.GetInputDevice(deviceType, LogicalPlayer.GetPlayerIndex(logicalPlayer));
        }

        /// <summary>
        /// Returns the input device of the given type of the given player
        /// </summary>
        /// <param name="playerIndex">The (physical) PlayerIndex of the player whose device you want to get.</param>
        /// <param name="deviceType">The type of the device you want to get.</param>
        /// <returns>The requested input device of type IControlDevice.</returns>
        public IControlDevice GetInputDevice(PlayerIndex playerIndex, DeviceType deviceType)
        {
            return inputDeviceManager.GetInputDevice(deviceType, playerIndex);
        }

#if WINDOWS
        public IDigitalControl GetInputDevice(MouseButtons button, LogicalPlayerIndex logicalPlayer)
        {
            return inputDeviceManager.GetInputDevice(button, LogicalPlayer.GetPlayerIndex(logicalPlayer));
        }

        public IPositionalControl GetInputDevice(MouseAxis axis, LogicalPlayerIndex logicalPlayer)
        {
            return inputDeviceManager.GetInputDevice(axis, LogicalPlayer.GetPlayerIndex(logicalPlayer));
        }
#endif

#if WINDOWS || XBOX
        public IDigitalControl GetInputDevice(Buttons button, LogicalPlayerIndex logicalPlayer)
        {
            return inputDeviceManager.GetInputDevice(button, LogicalPlayer.GetPlayerIndex(logicalPlayer));
        }

        public IDigitalControl GetInputDevice(Keys key, LogicalPlayerIndex logicalPlayer)
        {
            return inputDeviceManager.GetInputDevice(key, LogicalPlayer.GetPlayerIndex(logicalPlayer));
        }

        public IPositionalControl GetInputDevice(GamePadAxis axis, LogicalPlayerIndex logicalPlayer)
        {
            return inputDeviceManager.GetInputDevice(axis, LogicalPlayer.GetPlayerIndex(logicalPlayer));
        }
#endif

#if WINDOWS_PHONE
        public IDigitalControl GetInputDevice(PhoneButtons button, LogicalPlayerIndex logicalPlayer)
        {
            return inputDeviceManager.GetInputDevice(button, LogicalPlayer.GetPlayerIndex(logicalPlayer));
        }

        public IPositionalControl GetInputDevice(MultitouchIndex touchIndex, LogicalPlayerIndex logicalPlayer)
        {
            return inputDeviceManager.GetInputDevice(touchIndex, LogicalPlayer.GetPlayerIndex(logicalPlayer));
        }
#endif

#if WINDOWS_PHONE || WINDOWS
        public IPositional3DControl GetInputDevice(starLiGHT.Input.AccelerometerType accType, LogicalPlayerIndex logicalPlayer)
        {
            return inputDeviceManager.GetInputDevice(accType, LogicalPlayer.GetPlayerIndex(logicalPlayer));
        }
#endif

        public IEnumerable<IControlDevice> InputDevices
        {
            get { return inputDeviceManager.InputDevices; }
        }

        public ReadOnlyCollection<ControlScheme> ControlSchemes
        {
            get { return new ReadOnlyCollection<ControlScheme>(controlSchemes); }
        }

        ControlScheme lastActiveControlScheme;
        public ControlScheme ActiveControlScheme
        {
            get 
            {
#if WINDOWS_PHONE || XBOX
                throw new NotImplementedException();
#else
                if (lastActiveControlScheme == null && lastActiveControlScheme.Name != activeControlScheme) 
                    lastActiveControlScheme = controlSchemes.Find(acs => acs.Name == activeControlScheme);
                return lastActiveControlScheme;
#endif
            }
            set 
            { 
                activeControlScheme = value.Name; 
                lastActiveControlScheme = value;
            }
        }
    }
}
